// 获取需要操作的 DOM 对象
// 获取 AI 对话框部分
let chatContainer = document.getElementById("chatContainer");
// 获取用户输入的问题
let userInput = document.getElementById("userInput");
// 获取发送按钮
let sendButton = document.getElementById("sendButton");


// 事件绑定
// 点击按钮可以发送消息
sendButton.addEventListener('click',sendMessage);
// 按回车键也可以发送消息
userInput.addEventListener('keypress',(e) => {
    if(e.key === 'Enter'){
        e.preventDefault();
        sendMessage();
    }
})

// 发送消息
async function sendMessage(){
    // 登录验证。
    const token = localStorage.getItem("token");
    // 若不存在 token 说明没有登录，则踢回登录页面
    if(!token){
        window.location.href = '/';
    }
    // 获取用户输入的消息。最好去掉左右多余的空行。
    const message = userInput.value.trim();
    if(!message){
        // 验证消息不能为空
        return;
    }
    // 将来很多消息包括系统 AI 消息都会展示到面板中，所以我们定义一个方法即可。
    // 定义一个向AI 对话面板部分添加消息。
    // 1.消息         2.user || ai        3.角色 '用户'  '小狼'     4.小图标
    addMessage(message,'user','用户','bi-question-octagon-fill');
    // 清空输入框
    userInput.value = '';
    // 生成 AI，这里展示的是等待的图标
    showAiOutputStream();
    // 向后台发送请求（请求到 --> 服务器 --> 向量数据库--> 大模型 --> 返回数据）
    // 返回的数据是流的形式  ... ... .. ..
    try{
        // 获取用户信息，用来做记忆存储。记忆对话。知道哪个用户对应记录的对话。
        const userInfo = localStorage.getItem("userInfo");
        // 发送 ajax（异步） 请求，携带用户输入的消息和用户 id 作为参数。
        const response = await fetch(
            '/api/ai/chat',
            {
                method: 'POST',
                headers: {
                    'Content-Type':'application/json',  // 响应 JSON 格式字符串。
                },
                // 参数也要传递一个 JSON 格式字符串
                // JSON.parse(JSON格式字符串) 若该字符串满足 JS 对象格式，那么则转换为 JS 对象。
                // 这样就可以通过对象名.属性名 拿到用户ID

                // JSON.stringify("JS对象")  将 JS 对象转换为 JSON 字符串
                body: JSON.stringify({content : message , sender:JSON.parse(userInfo).id})
            }
        )
        // 等待后台返回消息，根据返回的状态码
        if(response.status === 200){
            // 成功了
            // 移除等待动画，准备将返回的数据展示到页面中
            removeAiStyle();
            // 调用向 AI 对话框添加信息的方法，将 AI 信息添加到对话框中。
            // 因为返回的是一个流。需要一直向 AI 部分对话中追加信息。所以我们需要调用这个方法后返回展示内容消息的 DIV。
            const aiMessageContent =
                addMessage("",'ai','狼狼','bi-android');
            // 系统返回的结果是一个流。获取到系统返回的流
            const reader = response.body.getReader();
            // 获取一个编码器， TextDecoder 用于将字节数据 解码为 字符串。
            const decoder = new TextDecoder();
            // 定义一个变量，用来接收并拼接每次返回的数据。最终将这个变量展示到页面中。一点一点的呈现的效果。
            let responseText = "";
            // 做一个死循环，因为我们并不知道后台能给我们返回多少条消息。他读取的方式是一条一条读。直到读不到下一条了，结束循环。
            while(true){
                // 后台返回的数据是两个
                // {value,done}    value = 代表每次返回的数据    done = true 就代表读完了。
                const {value,done} = await reader.read();
                // 判断是否读完了。
                if(done) {
                    // 读完了
                    break;
                }
                // 处理二进制数据解码。针对返回的 value 进行解码。
                // value 是需要解码的数据  {stream : true} 。 表示启用流式处理模式。
                const text = decoder.decode(value,{stream : true});
                // 切分字符串。切分成每一行数据
                const lines = text.split("\n");
                // 循环遍历这些切分好的行。
                for(const line of lines){
                    // 得到每一行数据。
                    // data:你好
                    //
                    // data:哈哈哈
                    // data:我是
                    // data:大狼
                    // line.startsWith("data:")  只要以  data: 开头的数据
                    if(line.startsWith("data:")){
                        // 切分字符串。将 data: 排除。按索引算 从5的位置开始是我们需要的字符串。
                        const content = line.slice(5).trim()
                        // 若存在数据
                        if(content){
                            // 拼接到展示数据部分中
                            responseText += content;
                            // 将内容设置到 AI 对话框输出消息部分
                            aiMessageContent.innerHTML = responseText;
                            // 添加滚动条
                            window.scrollTo({
                                top:document.body.scrollHeight,     // 页面文档高度
                                behavior:'smooth'                   // 丝滑滚动
                            })
                        }
                    }
                }
            }
        } else {
            // 失败了
            addMessage("出错啦~( ╯□╰ )~出错啦~( ╯□╰ )~，请稍后再试",'ai','狼狼','bi-android');
        }
    } catch (e){
        addMessage("崩溃啦崩溃啦( ╯□╰ )，请联系管理员~",'ai','狼狼','bi-android');
    }
    // 添加滚动条
    window.scrollTo({
        top:document.body.scrollHeight,     // 页面文档高度
        behavior:'smooth'                   // 丝滑滚动
    })
}

// 向 AI 对话框面板中添加消息
function addMessage(content,type,role,icon){
    // 创建一个 DIV，添加样式，添加内容，添加小图标
    // 创建一个 DIV
    const messageDiv = document.createElement('div');
    // 添加 class 属性 message user
    messageDiv.className = `message ${type}`;
    // 添加内容
    messageDiv.innerHTML = `
                    <div class="message-header"><i class="bi ${icon}"></i>&nbsp;&nbsp;${role}</div>
                    <div class="message-content">${content}</div>
                `;
    // 将生成的 div 添加到 AI 对话面板中。
    chatContainer.appendChild(messageDiv);
    return messageDiv.querySelector('.message-content');
}

// AI 等待样式
function showAiOutputStream(){
    // 创建一个 DIV
    const indicator = document.createElement('div');
    // 添加 class 属性
    indicator.className = 'message ai typing-indicator';
    // 为生成的 DIV 添加一个 id 属性 <div class='message ai typing-indicator' id='typingIndicator'></div>
    indicator.id = 'typingIndicator';
    // 添加内容
    indicator.innerHTML = `
                    <div class="message-header"><i class="bi bi-emoji-smile-upside-down-fill"></i>&nbsp;&nbsp;大狼</div>
                    <div class="typing-dot"></div>
                    <div class="typing-dot"></div>
                    <div class="typing-dot"></div>
                `;
    // 将生成的 div 添加到 AI 对话面板中。
    chatContainer.appendChild(indicator);
}

// 移除 AI 样式
function removeAiStyle(){
    const aiStyleDiv = document.getElementById("typingIndicator");
    // 判断若存在则删除
    if(aiStyleDiv){
        aiStyleDiv.remove();
    }
}